/**
* This file handles all server related functions
*/

//-------------------------------------------------------
// Comments & File Logging
//-------------------------------------------------------
/**
* @author Soon
* @date 26th January 2014
* TODO: Create wrapper for error and return for all network related functions

* COMMENT: Code Convention:
* @param paramName description
* @return returnValue description
* @author YourName
* FIXME: Anything that needs to be fixed in the code
* TODO: Anything else that needs to be done in the code
* COMMENT: For communication between teammates
* Commenting style:
* i)   // For inside function comments and short comments
* ii)  /** For function descriptions, file descriptions, communication between teammates
* iii) //-------------------------------------------------------
*      // For the beginning of any section in code
*      //-------------------------------------------------------
* iv) /* To be assigned later
*/

//-------------------------------------------------------
// Header Files
//-------------------------------------------------------
#include <stdio.h> // for File
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <assert.h>
#include <signal.h>
#include "utils.h"
#include "shared.h" // for logger()

//-------------------------------------------------------
// Constants
//-------------------------------------------------------
#define MAX_LISTENQUEUELEN 20   // The maximum number of queued connections.
#define LOGGING 2
    // 0 -> disabled logging
    // 1 -> log to stdout
    // 2 -> log to file that follows format: Client/Server-yyyy-mm-dd-hh-ss.log
FILE *fileServer; // file to be used for logger() in server.c

//-------------------------------------------------------
// Function Prototypes
//-------------------------------------------------------
int handle_command(int sock, char *cmd);

//-------------------------------------------------------
// Main
//-------------------------------------------------------
/**
* Main function that parses in commandline arguments and initializes the server
* @param argv[1] configurationFile
*/
int main(int argc, char *argv[])
{
    // Process command line arguments.
    // This program expects exactly one argument: the config file name.
    assert(argc > 0); // aborts program if this is false
    if (argc != 2)
    {
	printf("Usage %s <config_file>\n", argv[0]);
	exit(EXIT_FAILURE);
    }
    // Configuration file
    char *config_file = argv[1];
    // Read the config_file and store into the config_params struct
    struct config_params params;
    int status = read_config(config_file, &params); // returns 0 if successful
    if (status != 0)
    {
	perror("Error processing config file.");
	exit(EXIT_FAILURE);
    }
    // Set up time for logging
    char dateS[5], hourS[5], monthS[5], minS[5], secS[5];
    int year;
    // Get the current time and store into strings
    setupTime(&dateS, &hourS, &monthS, &minS, &secS, &year);
    char fileName[MAX_STRTYPE_SIZE];
    sprintf(fileName,"Server-%d-%s-%s-%s-%s-%s.log", year, monthS, dateS, hourS, minS, secS );
    int logChoice = LOGGING;
    // Initialize fileServer based on LOGGING
    switch(logChoice)
    {
	case 0: // Disable Logging
	    fileServer = NULL;
	    break;
	case 1: // Log to stdout
	    fileServer = stdout;
	    break;
	case 2: // Log to File
	    // Append file for appending an update
	    fileServer = fopen (fileName,"a+");
	    if (fileServer == NULL)
	    {
		printf("Error opening file.\n");
		exit(EXIT_FAILURE);
	    }
	    break;
    }
    // Server on localhost:1376
    sprintf(message, "Server on %s:%d\n", params.server_host, params.server_port);
    logger (fileServer, message);
    // Create a socket for the server
    // listensock is the sockfd to be listening to
    int listensock = socket(PF_INET, SOCK_STREAM, 0);
    if (listensock < 0)
    {
	perror("Error creating socket.");
	exit(EXIT_FAILURE);
    }
    // Allow listening port to be reused if defunctional
    int yes = 1;
    status = setsockopt(listensock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes);
    if (status != 0)
    {
	perror("Error configuring socket.");
	exit(EXIT_FAILURE);
    }
    // Bind to the listening port.
    struct sockaddr_in listenaddr;
    memset(&listenaddr, 0, sizeof listenaddr);
    listenaddr.sin_family = AF_INET;
    listenaddr.sin_port = htons(params.server_port);
    inet_pton(AF_INET, params.server_host, &(listenaddr.sin_addr)); // bind to local IP address
    // Let server bind to this socket
    status = bind(listensock, (struct sockaddr*) &listenaddr, sizeof listenaddr);
    if (status != 0)
    {
	perror("Error binding socket.");
	exit(EXIT_FAILURE);
    }
    // Start listening for connections.
    status = listen(listensock, MAX_LISTENQUEUELEN);
    if (status != 0)
    {
	perror("Error listening on socket.");
	exit(EXIT_FAILURE);
    }
    // Listen loop.
    int wait_for_connections = 1;
    // Infinite while loop for milestone 1
    while (wait_for_connections)
    {
	// Wait for a connection.
	struct sockaddr_in clientaddr;
	socklen_t clientaddrlen = sizeof clientaddr;
	// Accept any calls from any client
	// @note: Will just poll till a client is accepted
	int clientsock = accept(listensock, (struct sockaddr*)&clientaddr, &clientaddrlen);
	// Accepted client wil return it's file descriptor as clientsock
	if (clientsock < 0)
	{
	    perror("Error accepting a connection.");
	    exit(EXIT_FAILURE);
	}
	sprintf(message, "Got a connection from %s:%d.\n", inet_ntoa(clientaddr.sin_addr), clientaddr.sin_port);
	logger(fileServer, message);
	// Get commands from client.
	int wait_for_commands = 1;
	do
	{
	    // Read a line from the client.
	    char cmd[MAX_CMD_LEN];
	    // Read from clientsock's file descriptor to command
	    int status = recvline(clientsock, cmd, MAX_CMD_LEN);
	    if (status != 0)
	    {
		// Here, either an error occurred or the client closed the connection.
		// Stop waiting for commands
		wait_for_commands = 0;
	    }
	    else
	    {
		// Handle the command from the client.
		// @note: For milestone1, this just prints it back out to server
		int status = handle_command(clientsock, cmd);
		// If handled command fails, stop waiting for command
		if (status != 0)
		// TODO: Print error message
		    wait_for_commands = 0; // Oops.  An error occured.
            }
         }
	while (wait_for_commands); //do while client is still interacting
        // Close the connection with the client.
	close(clientsock);
	sprintf(message, "Closed connection from %s:%d.\n", inet_ntoa(clientaddr.sin_addr), clientaddr.sin_port);
	logger(fileServer, message);
    }
    // Stop listening for connections.
    close(listensock);
    return EXIT_SUCCESS;
}

//-------------------------------------------------------
// Helper Functions
//-------------------------------------------------------
/**
* This function handles a command given by sending it as a string to a socket given.
* @param sock The socket descriptor to output commands to
* @param cmd A string to be sent to a socket descriptor
* @return 0 if success
*/
int handle_command(int sock, char *cmd)
{
    sprintf(message, "Processing command '%s'\n", cmd);
    logger(fileServer, message); //    LOG(("Processing command '%s'\n", cmd));
    // @note: sock is the socket file descriptor (sockfd)
    // Send the command out to sockfd, which is the server
    // and end with a new line
    sendall(sock, cmd, strlen(cmd));
    sendall(sock, "\n", 1);
    return 0;
}
